#include "../config.h"

#include <gtk/gtk.h>
#include <glibmm/i18n.h>

#include "log.h"
#include "application.h"
#include "command.h"
#include "widgets.h"
#include "gtkcellrendererrelay.h"

namespace gpyp {

//========================
// TextMenuBase
void TextMenuBase::prepare_commandopts(CommandOpts &opts) {
  opts.ch = app_.get_selected_channel();
}

void TextMenuBase::create_menu(Gtk::Menu *menu) {
  Gtk::SeparatorMenuItem &sep = *Gtk::manage(new Gtk::SeparatorMenuItem());
  sep.show();
  menu->append(sep);
  ConfMenu::RefMenu text_menu = app_.conf().menu()->text();
  for(ConfMenu::Menu::iterator iter = text_menu->begin();
      iter != text_menu->end(); ++iter) {
    Gtk::MenuItem *mi = NULL;
    switch((*iter)->get_type()) {
    case ConfMenu::ITEM_TYPE_SEPARATOR:
      mi = Gtk::manage(new Gtk::SeparatorMenuItem());
      break;
    case ConfMenu::ITEM_TYPE_SPECIAL:
      mi = Gtk::manage(new SpecialMenuItem(app_, *iter, sigc::mem_fun(*this, &TextMenuBase::prepare_commandopts)));
      break;
    default:
      mi = Gtk::manage(new MenuItem(app_, *iter, sigc::mem_fun(*this, &TextMenuBase::prepare_commandopts)));
      break;
    }
    mi->show();
    menu->append(*mi);
  }
}

TextMenuBase::TextMenuBase(Application &app) :
  app_(app)
{
}
TextMenuBase::~TextMenuBase() {
}

//========================
// Entry
void Entry::prepare_commandopts(CommandOpts &opts) {
  TextMenu<Gtk::Entry>::prepare_commandopts(opts);
  int s, e;
  if (get_selection_bounds(s, e)) {
    text_ = get_chars(s, e);
  } else {
    text_.clear();
  }
  opts.text = text_.c_str();
}

Entry::Entry(Application &app) :
  TextMenu<Gtk::Entry>(app)
{
}

//========================
// EntryLabel
void EntryLabel::on_realize() {
  Entry::on_realize();
  update_color();
}
void EntryLabel::on_parent_changed(Gtk::Widget */*previous_parent*/) {
  conn_parent_style_changed_.disconnect();

  Gtk::Container *parent = get_parent();
  if (parent) {
    conn_parent_style_changed_ = parent->signal_style_changed()
      .connect(sigc::mem_fun(*this, &EntryLabel::parent_on_style_changed));
  }
}

void EntryLabel::parent_on_style_changed(const Glib::RefPtr<Gtk::Style> &/*previous_style*/) {
  update_color();
}

void EntryLabel::update_color() {
  Gtk::Container *parent = get_parent();
  if (parent) {
    GtkWidget *w = GTK_WIDGET(gobj());
    GtkStyle  *s = GTK_WIDGET(parent->gobj())->style;
    gtk_widget_modify_base(w, GTK_STATE_NORMAL, &s->bg[GTK_STATE_NORMAL]);
    gtk_widget_modify_text(w, GTK_STATE_NORMAL, &s->fg[GTK_STATE_NORMAL]);
  }
}

EntryLabel::EntryLabel(Application &app) :
  Entry(app)
{
  set_editable(false);
  set_has_frame(false);

#if GTK_CHECK_VERSION(2, 10, 0)
  GtkBorder border = {0, 0, 0, 0};
  set_inner_border(border);
#endif
}
EntryLabel::~EntryLabel() {
  conn_parent_style_changed_.disconnect();
}

//========================
// Label
void Label::prepare_commandopts(CommandOpts &opts) {
  TextMenu<Gtk::Label>::prepare_commandopts(opts);
  int s, e;
  if (get_selection_bounds(s, e)) {
    text_ = get_text().substr(s, e - s);
  } else {
    text_.clear();
  }
  opts.text = text_.c_str();
}

Label::Label(Application &app, const Glib::ustring &label) :
  TextMenu<Gtk::Label>(app)
{
  set_label(label);
  set_alignment(Gtk::ALIGN_LEFT);
  set_selectable(true);
}

//========================
// TextView
void TextView::prepare_commandopts(CommandOpts &opts) {
  TextMenu<Gtk::TextView>::prepare_commandopts(opts);
  Gtk::TextIter s, e;
  if (get_buffer()->get_selection_bounds(s, e)) {
    text_ = get_buffer()->get_text(s, e);
  } else {
    text_.clear();
  }
  opts.text = text_.c_str();
}

void TextView::on_changed() {
  clear_search();
}

void TextView::set_monospace_font() {
  Pango::FontDescription fd(*app_.conf().general()->font_monospace());
  modify_font(fd);
}

void TextView::clear_search() {
  if (tag_search_) {
    get_buffer()->get_tag_table()->remove(tag_search_);
    tag_search_.clear();
  }
}

void TextView::search(RefRegex re) {
  Glib::RefPtr<Gtk::TextBuffer> buf = get_buffer();

  clear_search();
  if (re) {
    tag_search_ = buf->create_tag();
    tag_search_->property_background_gdk() = Gdk::Color("#008000");
    tag_search_->property_foreground_gdk() = Gdk::Color("white");

    Glib::ustring text = buf->get_text();
    std::map<long, long> matches = re->search_all(text);
    for(std::map<long, long>::iterator iter = matches.begin();
        iter != matches.end(); ++iter) {
      Gtk::TextIter begin = buf->get_iter_at_offset(iter->first);
      Gtk::TextIter end = buf->get_iter_at_offset(iter->first + iter->second);
      buf->apply_tag(tag_search_, begin, end);
    }
  }
}

TextView::TextView(Application &app, bool monospace, Gtk::WrapMode wrap) :
  TextMenu<Gtk::TextView>(app),
  app_                   (app)
{
  get_buffer()->signal_changed()
    .connect(sigc::mem_fun(*this, &TextView::on_changed));

  if (monospace) {
    set_monospace_font();
    app_.conf().general()->font_monospace()->signal_changed()
      .connect(sigc::mem_fun(*this, &TextView::set_monospace_font));
  }
  set_wrap_mode(wrap);
}

//========================
// HostEntry
void HostEntry::attr_on_changed() {
#ifdef GTK_ENTRY_HAS_ICON
  if (attr_->valid()) {
    gtk_entry_set_icon_from_pixbuf(gobj(), GTK_ENTRY_ICON_SECONDARY, NULL);
  } else if (!attr_->valid()) {
    set_icon_from_stock(Gtk::Stock::DIALOG_ERROR, Gtk::ENTRY_ICON_SECONDARY);
  } else {
    set_icon_from_stock(Gtk::Stock::DIALOG_WARNING, Gtk::ENTRY_ICON_SECONDARY);
  }
#else
  modify_base(Gtk::STATE_NORMAL, (attr_->valid()
                                  ? get_default_style()->get_base(Gtk::STATE_NORMAL)
                                  : (!attr_->valid() ? Gdk::Color("red") : Gdk::Color("yellow"))));
#endif
}

void HostEntry::set(RefAttributeHost attr) {
  conn_attr_changed_.disconnect();
  if (attr_) attr_->detach(property_text());

  attr_ = attr;
  attr_->attach(property_text());

  attr_on_changed();
  conn_attr_changed_ = attr_->signal_changed()
    .connect(sigc::mem_fun(*this, &HostEntry::attr_on_changed));
}

HostEntry::HostEntry(RefAttributeHost attr) {
  set(attr);
}
HostEntry::~HostEntry() {
  conn_attr_changed_.disconnect();
}

//========================
// TreeView::Tooltip
const int TreeView::Tooltip::MIN_WIDTH = 64;

void TreeView::Tooltip::on_realize() {
  gtk_widget_set_parent_window(GTK_WIDGET(gobj()), gtk_tree_view_get_bin_window(treeview_.gobj()));

  Gtk::Widget::on_realize();
  ensure_style();

  GdkWindowAttr wndattr;
  memset(&wndattr, 0, sizeof(wndattr));
  Gtk::Allocation allocation = get_allocation();
  wndattr.x           = allocation.get_x();
  wndattr.y           = allocation.get_y();
  wndattr.width       = allocation.get_width();
  wndattr.height      = allocation.get_height();
  wndattr.event_mask  = get_events() | Gdk::BUTTON_PRESS_MASK | Gdk::EXPOSURE_MASK;
  wndattr.window_type = GDK_WINDOW_CHILD;
  wndattr.wclass      = GDK_INPUT_OUTPUT;

  /* Gtk::Widget::set_window()は2.18でクラッシュする。
     gtk_widget_set_window()がGdkWindowの参照カウントを増やさないせい。 */
  GdkWindow *window = gdk_window_new(get_parent_window()->gobj(),
                                     &wndattr, GDK_WA_X | GDK_WA_Y);
  unset_flags(Gtk::NO_WINDOW);
#if GTK_CHECK_VERSION(2, 18, 0)
  gtk_widget_set_window(GTK_WIDGET(gobj()), window);
#else
  /* gtk_widget_set_window()からコピペ */
  GtkWidget *widget = GTK_WIDGET(gobj());
  widget->window = window;
# if GTK_CHECK_VERSION(2, 14, 0)
  g_object_notify(G_OBJECT(widget), "window");
# endif
#endif
  gdk_window_set_user_data(window, gobj());
}
void TreeView::Tooltip::on_unrealize() {
  Gtk::Widget::on_unrealize();
  set_flags(Gtk::NO_WINDOW);
}
bool TreeView::Tooltip::on_expose_event(GdkEventExpose */*event*/) {
  if (!tree_model_) return false;

  GdkColor *save_cell_background_gdk_p = NULL;
  GdkColor *save_background_gdk_p      = NULL;
  GdkColor *save_foreground_gdk_p      = NULL;
  GdkColor  save_cell_background_gdk;
  GdkColor  save_background_gdk;
  GdkColor  save_foreground_gdk;
  gboolean  save_cell_background_set = FALSE;
  gboolean  save_background_set = FALSE;
  gboolean  save_foreground_set = FALSE;
  tree_view_column_->cell_set_cell_data(tree_model_, tree_iter_, is_expander_, is_expanded_);
  g_object_get(renderer_,
               "cell-background-gdk", &save_cell_background_gdk_p,
               "background-gdk",      &save_background_gdk_p,
               "foreground-gdk",      &save_foreground_gdk_p,
               "cell-background-set", &save_cell_background_set,
               "background-set",      &save_background_set,
               "foreground-set",      &save_foreground_set,
               NULL);
#define SET_COLOR(D, S) memcpy(&D, (S), sizeof(D))
  SET_COLOR(save_cell_background_gdk, save_cell_background_gdk_p);
  SET_COLOR(save_background_gdk, save_background_gdk_p);
  SET_COLOR(save_foreground_gdk, save_foreground_gdk_p);
#undef SET_COLOR

  bool sel = (selected_iter_ &&
              tree_iter_.get_stamp() == selected_iter_.get_stamp() &&
              tree_iter_ == selected_iter_);
  const GdkColor *cell_bg = NULL, *bg = NULL, *fg = NULL;
  if (save_foreground_set) {
    fg = &save_foreground_gdk;
  } else {
    const Color &c_f = sel ? *conf_->color_fg_sel() : *conf_->color_fg();
    if (c_f) {
      fg = c_f.gobj();
    } else {
      fg = &GTK_WIDGET(treeview_.gobj())->style->text[sel ? GTK_STATE_SELECTED : GTK_STATE_NORMAL];
    }
  }
  {
    const Color &c_c = sel ? *conf_->color_fr_sel() : *conf_->color_fr();
    const Color &c_b = sel ? *conf_->color_bg_sel() : *conf_->color_bg();
    if (c_b) {
      bg = c_b.gobj();
    } else if (sel) {
      bg = &GTK_WIDGET(treeview_.gobj())->style->base[GTK_STATE_SELECTED];
    } else if (save_background_set) {
      bg = &save_background_gdk;
    } else if (save_cell_background_set) {
      bg = &save_cell_background_gdk;
    } else {
      bg = &GTK_WIDGET(treeview_.gobj())->style->base[GTK_STATE_NORMAL];
    }
    if (sel ? *conf_->enable_frame_sel() : *conf_->enable_frame()) {
      if (c_c) {
        cell_bg = c_c.gobj();
      } else {
        cell_bg = fg;
      }
    } else {
      cell_bg = bg;
    }
  }
  g_object_set(renderer_,
               "cell-background-gdk", cell_bg,
               "background-gdk",      bg,
               "foreground-gdk",      fg,
               "ellipsize-set",       FALSE,
               "wrap-mode",           wrap_mode_,
               "wrap-width",          cell_area_.width,
               "xpad",                xpad_,
               "ypad",                ypad_,
               NULL);
  gtk_cell_renderer_render(GTK_CELL_RENDERER(renderer_),
                           get_window()->gobj(),
                           gobj(),
                           &background_area_,
                           &cell_area_,
                           &cell_area_,
                           GTK_CELL_RENDERER_PRELIT);
  g_object_set(renderer_,
               "cell-background-gdk", &save_cell_background_gdk,
               "background-gdk",      &save_background_gdk,
               "foreground-gdk",      &save_foreground_gdk,

               "cell-background-set", save_cell_background_set,
               "background-set",      save_background_set,
               "foreground-set",      save_foreground_set,

               "ellipsize",           save_ellipsize_,
               "ellipsize-set",       save_ellipsize_set_,
               "wrap-mode",           save_wrap_mode_,
               "wrap-width",          save_wrap_width_,
               "xpad",                save_xpad_,
               "ypad",                save_ypad_,
               NULL);
  return true;
}

void TreeView::Tooltip::set(GtkCellRendererText         *renderer,
                            PangoEllipsizeMode           save_ellipsize,
                            gboolean                     save_ellipsize_set,
                            PangoWrapMode                save_wrap_mode,
                            gint                         save_wrap_width,
                            guint                        save_xpad,
                            guint                        save_ypad,
                            Gtk::TreeViewColumn         *tree_view_column,
                            Glib::RefPtr<Gtk::TreeModel> tree_model,
                            Gtk::TreeIter               &tree_iter,
                            bool                         is_expander,
                            bool                         is_expanded) {
  renderer_           = renderer;
  save_ellipsize_     = save_ellipsize;
  save_ellipsize_set_ = save_ellipsize_set;
  save_wrap_mode_     = save_wrap_mode;
  save_wrap_width_    = save_wrap_width;
  save_xpad_          = save_xpad;
  save_ypad_          = save_ypad;
  tree_view_column_   = tree_view_column;
  tree_model_         = tree_model;
  tree_iter_          = tree_iter;
  is_expander_        = is_expander;
  is_expanded_        = is_expanded;
}
#define SET_RECT(R, X, Y, W, H) {               \
    R.x      = X;                               \
    R.y      = Y;                               \
    R.width  = W;                               \
    R.height = H;                               \
  }
void TreeView::Tooltip::set_background_area(int x, int y, int w, int h) {
  SET_RECT(background_area_, x, y, w, h);
}
void TreeView::Tooltip::set_cell_area(int x, int y, int w, int h) {
  SET_RECT(cell_area_, x, y, w, h);
}
#undef SET_RECT

TreeView::Tooltip::Tooltip(Application &app, Gtk::TreeView &treeview) :
  app_             (app),
  treeview_        (treeview),
  conf_            (app.conf().general()->tooltip()),
  wrap_mode_       (DEFAULT_TOOLTIP_WRAP_MODE),
  xpad_            (1),
  ypad_            (1),
  renderer_        (NULL),
  tree_view_column_(NULL),
  last_column_     (NULL)
{
  set_flags(Gtk::NO_WINDOW);
  set_no_show_all(true);
}
TreeView::Tooltip::~Tooltip() {
}

//========================
// TreeView
PangoWrapMode TreeView::DEFAULT_TOOLTIP_WRAP_MODE = PANGO_WRAP_WORD_CHAR;

bool TreeView::on_button_press_event(GdkEventButton *event) {
  Gtk::TreePath         path;
  Gtk::TreeViewColumn  *column = NULL;
  int                   cell_x, cell_y;
  if (event->window == get_bin_window()->gobj() &&
      !get_path_at_pos(static_cast<int>(event->x), static_cast<int>(event->y),
                       path, column, cell_x, cell_y)) {
    get_selection()->unselect_all();
  }

  if (tooltip_ && event->window == GTK_WIDGET(tooltip_->gobj())->window) {
    int tx, ty, bx, by;
    tooltip_->get_window()->get_origin(tx, ty);
    get_bin_window()->get_origin(bx, by);
    event->x += tx - bx;
    event->y += ty - by;
    g_object_unref(event->window);
    event->window = get_bin_window()->gobj();
    g_object_ref(event->window);
  }
  return Gtk::TreeView::on_button_press_event(event);
}
void TreeView::on_size_allocate(Gtk::Allocation &allocation) {
  Gtk::TreeView::on_size_allocate(allocation);
  if (restore_scroll_value_) {
    hadj_on_value_changed();
    vadj_on_value_changed();
    if (tooltip_) {
      tooltip_->clear_last();
      update_tooltip();
    }
    get_bin_window()->thaw_updates();
    get_window()->thaw_updates();
    restore_scroll_value_ = false;
  }
}

void TreeView::hadj_on_value_changed() {
  if (restore_scroll_value_) {
    Gtk::Adjustment *hadj = get_hadjustment();
    double hl = hadj->get_lower(), hu = hadj->get_upper() - hadj->get_page_size();
    if (scroll_hvalue_ < hl) scroll_hvalue_ = hl;
    if (scroll_hvalue_ > hu) scroll_hvalue_ = hu;
    conn_hadj_value_changed_.block();
    hadj->set_value(scroll_hvalue_);
    conn_hadj_value_changed_.unblock();
  } else if (is_mapped()) {
    update_tooltip();
  }
}
void TreeView::vadj_on_value_changed() {
  if (restore_scroll_value_) {
    Gtk::Adjustment *vadj = get_vadjustment();
    double vl = vadj->get_lower(), vu = vadj->get_upper() - vadj->get_page_size();
    if (scroll_vvalue_ < vl) scroll_vvalue_ = vl;
    if (scroll_vvalue_ > vu) scroll_vvalue_ = vu;
    conn_vadj_value_changed_.block();
    vadj->set_value(scroll_vvalue_);
    conn_vadj_value_changed_.unblock();
  } else if (is_mapped()) {
    update_tooltip();
  }
}

void TreeView::on_set_scroll_adjustments(Gtk::Adjustment *hadjustment,
                                         Gtk::Adjustment *vadjustment) {
  Gtk::TreeView::on_set_scroll_adjustments(hadjustment, vadjustment);
#define CONN_ADJ(N) {                                                   \
    conn_##N##adj_value_changed_.disconnect();                          \
    if (N##adjustment) {                                                \
      conn_##N##adj_value_changed_ = N##adjustment->signal_value_changed() \
        .connect(sigc::mem_fun(*this, &TreeView::N##adj_on_value_changed)); \
    }                                                                   \
  }
  CONN_ADJ(h);
  CONN_ADJ(v);
#undef CONN_ADJ
}

void TreeView::store_scroll_value() {
  if (is_realized()) {
    scroll_hvalue_ = get_hadjustment()->get_value();
    scroll_vvalue_ = get_vadjustment()->get_value();
    restore_scroll_value_ = true;
    gdk_window_freeze_updates(GTK_WIDGET(gobj())->window);
    gdk_window_freeze_updates(gtk_tree_view_get_bin_window(gobj()));
  }
}

void TreeView::update_tooltip_pos(int x, int y) {
  if (!tooltip_ || !is_realized()) return;

  Gtk::TreePath        path;
  Gtk::TreeViewColumn *column;
  int cell_x, cell_y;
  if (get_path_at_pos(x, y, path, column, cell_x, cell_y)) {
    if (!tooltip_->last_column_ ||
        path != tooltip_->last_path_ || column != tooltip_->last_column_) {
      tooltip_->hide();
      tooltip_->last_path_   = path;
      tooltip_->last_column_ = column;

      GtkCellRendererText *cell_text = NULL;
      {
        gpointer cell = g_hash_table_lookup(cache_renderer_, column->gobj());
        if (!cell) return;
        cell_text = GTK_CELL_RENDERER_TEXT(cell);
      }

      PangoEllipsizeMode save_ellipsize     = PANGO_ELLIPSIZE_NONE;
      gboolean           save_ellipsize_set = FALSE;
      PangoWrapMode      save_wrap_mode     = tooltip_->wrap_mode_;
      gint               save_wrap_width    = 0;
      guint              save_xpad          = 0;
      guint              save_ypad          = 0;
      g_object_get(cell_text,
                   "ellipsize",     &save_ellipsize,
                   "ellipsize-set", &save_ellipsize_set,
                   "wrap-mode",     &save_wrap_mode,
                   "wrap-width",    &save_wrap_width,
                   "xpad",          &save_xpad,
                   "ypad",          &save_ypad,
                   NULL);

      {
        gint hsep = 0, vsep = 0;
        gtk_widget_style_get(GTK_WIDGET(gobj()),
                             "horizontal-separator", &hsep,
                             "vertical-separator",   &vsep,
                             NULL);
        if (*tooltip_->conf_->xpad() < 0) {
          tooltip_->xpad_ = hsep / 2;
        } else {
          tooltip_->xpad_ = *tooltip_->conf_->xpad();
        }
        if (*tooltip_->conf_->ypad() < 0) {
          tooltip_->ypad_ = vsep / 2;
        } else {
          tooltip_->ypad_ = *tooltip_->conf_->ypad();
        }
      }


      Glib::RefPtr<Gtk::TreeModel> model = get_model();
      Gtk::TreeIter iter = model->get_iter(path);
      bool          is_expander = get_expander_column() == column;
      bool          is_expanded = row_expanded(path);
      column->cell_set_cell_data(model, iter, is_expander, is_expanded);

      int wx, wy, ww, wh, wd;
      get_bin_window()->get_geometry(wx, wy, ww, wh, wd);

      Gdk::Rectangle cell_area;
      get_cell_area(path, *column, cell_area);
      if (GTK_IS_CELL_RENDERER_RELAY(cell_text)) {
        gtk_cell_renderer_relay_set_relay_height(GTK_CELL_RENDERER_RELAY(cell_text),
                                                 cell_area.get_height() - 4);
      }

      gfloat xalign = 0;
      g_object_get(cell_text, "xalign", &xalign, NULL);
      gint max_width = 0;
      if (xalign == 0) {
        max_width = MAX(ww - cell_area.get_x(), Tooltip::MIN_WIDTH) - 2;
      } else if (xalign == 1) {
        max_width = MAX(cell_area.get_x() + cell_area.get_width(), Tooltip::MIN_WIDTH) - 2;
      } else {
        max_width = MAX(cell_area.get_width(), Tooltip::MIN_WIDTH) - 2;
      }

      g_object_set(cell_text,
                   "ellipsize",  FALSE,
                   "wrap-width", -1,
                   NULL);

      int x = 0, y = 0, w = 0, h = 0;
      /* get_size()で帰るサイズは((xpad|ypad) * 2)を含む */
      gtk_cell_renderer_get_size(GTK_CELL_RENDERER(cell_text),
                                 GTK_WIDGET(gobj()),
                                 NULL,
                                 &x, &y, &w, &h);

      if (w > cell_area.get_width() - 2) {
        g_object_set(cell_text,
                     "wrap-mode",  tooltip_->wrap_mode_,
                     "wrap-width", max_width - (save_xpad * 2) - tooltip_->xpad_,
                     "xpad",       tooltip_->xpad_,
                     "ypad",       tooltip_->ypad_,
                     NULL);
        gtk_cell_renderer_get_size(GTK_CELL_RENDERER(cell_text),
                                   GTK_WIDGET(gobj()),
                                   NULL,
                                   &x, &y, &w, &h);
        if (cell_area.get_height() > h) h = cell_area.get_height();

        tooltip_->set(cell_text,
                      save_ellipsize,
                      save_ellipsize_set,
                      save_wrap_mode,
                      save_wrap_width,
                      save_xpad,
                      save_ypad,
                      column,
                      model,
                      iter,
                      is_expander,
                      is_expanded);
        tooltip_->set_background_area(0, 0, w + 2, h + 2);
        tooltip_->set_cell_area(1, 1, w, h);

        int tx = cell_area.get_x(), ty = cell_area.get_y(), tw = w + 2, th = h + 2;
        if (xalign == 1) tx = cell_area.get_x() + cell_area.get_width() - tw - 1;
        tx += save_xpad - tooltip_->xpad_;
        ty += save_ypad - tooltip_->ypad_;
        if (tx + tw > ww) tx = ww - tw;
        if (ty + th > wh) ty = wh - th;
        if (tx < 0) tx = 0;
        if (ty < 0) ty = 0;

        GdkRectangle alloc = {tx, ty, tw, th};
        tooltip_->size_allocate(alloc);
        tooltip_->show();
        tooltip_->queue_draw();
      }
      g_object_set(cell_text,
                   "ellipsize",     save_ellipsize,
                   "ellipsize-set", save_ellipsize_set,
                   "wrap-mode",     save_wrap_mode,
                   "wrap-width",    save_wrap_width,
                   "xpad",          save_xpad,
                   "ypad",          save_ypad,
                   NULL);
    } else if (tooltip_->is_visible()) {
      tooltip_->queue_draw();
    }
  } else {
    tooltip_->last_column_ = NULL;
    tooltip_->hide();
  }
}
void TreeView::update_tooltip() {
  if (tooltip_ && is_realized()) {
    GdkWindow *bin = gtk_tree_view_get_bin_window(gobj());
    gint x = 0, y = 0;
    GdkModifierType mask;
    gdk_window_get_pointer(bin, &x, &y, &mask);
    update_tooltip_pos(x, y);
  }
}

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
GtkCellRenderer *TreeView::get_tooltip_renderer(GtkTreeViewColumn *column) {
  return NULL;
}
#endif

void TreeView::forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data) {
  Gtk::TreeView::forall_vfunc(include_internals, callback, callback_data);
  if (tooltip_) callback(tooltip_->gobj(), callback_data);
}
bool TreeView::on_motion_notify_event(GdkEventMotion *event) {
  if (tooltip_) {
    if (event->window != gtk_tree_view_get_bin_window(gobj())) {
      tooltip_->hide();
      return Gtk::TreeView::on_motion_notify_event(event);
    } else {
      update_tooltip_pos(static_cast<int>(event->x), static_cast<int>(event->y));
      return true;
    }
  } else {
    return Gtk::TreeView::on_motion_notify_event(event);
  }
}
bool TreeView::on_leave_notify_event(GdkEventCrossing *event) {
  if (tooltip_) {
    int x, y;
    Gdk::ModifierType mod;
    Glib::RefPtr<Gdk::Window> window = get_window()->get_pointer(x, y, mod);
    if (!window || (window != get_bin_window() && window != tooltip_->get_window())) {
      tooltip_->hide();
      tooltip_->clear_last();
    }
  }
  return Gtk::TreeView::on_leave_notify_event(event);
}
void TreeView::on_columns_changed() {
  Gtk::TreeView::on_columns_changed();
  /* on_motion_notify_eventでやると体感出来るレベルでもっさりするのでキャッシュする。 */
  if (tooltip_ && cache_renderer_) {
    GList *columns = gtk_tree_view_get_columns(gobj());
    for(; columns; columns = columns->next) {
      gpointer data = g_hash_table_lookup(cache_renderer_, columns->data);
      if (data == NULL) {
#ifdef GTK_CELL_LAYOUT_HAVE_GET_CELLS
        GList *renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(columns->data));
        if (g_list_length(renderers) == 1 && GTK_IS_CELL_RENDERER_TEXT(renderers->data)) {
          g_hash_table_insert(cache_renderer_, columns->data, renderers->data);
        }
        g_list_free(renderers);
#else
        GtkCellRenderer *renderer = get_tooltip_renderer(GTK_TREE_VIEW_COLUMN(columns->data));
        if (renderer && GTK_IS_CELL_RENDERER_TEXT(renderer)) {
          g_hash_table_insert(cache_renderer_, columns->data, renderer);
        }
#endif
      }
    }
    g_list_free(columns);
  }
}
void TreeView::selection_on_changed() {
  if (tooltip_) {
    tooltip_->selected_iter_ = get_selection()->get_selected();
    if (tooltip_->is_mapped()) tooltip_->queue_draw();
  }
}

TreeView::TreeView() :
  scroll_hvalue_       (0),
  scroll_vvalue_       (0),
  restore_scroll_value_(false),
  tooltip_             (NULL),
  cache_renderer_      (NULL)
{
  add_events(Gdk::BUTTON_PRESS_MASK);
  set_reallocate_redraws(false);
  set_redraw_on_allocate(false);

  get_selection()->signal_changed().connect(sigc::mem_fun(*this, &TreeView::selection_on_changed));
}
TreeView::TreeView(Application &app, bool enable_tooltip) :
  scroll_hvalue_       (0),
  scroll_vvalue_       (0),
  restore_scroll_value_(false),
  tooltip_             (NULL),
  cache_renderer_      (NULL)
{
  if (enable_tooltip) {
    tooltip_ = new Tooltip(app, *this);
    add_events(Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK);
    tooltip_->set_parent(*this);

    cache_renderer_ = g_hash_table_new(NULL, NULL);
  }

  add_events(Gdk::BUTTON_PRESS_MASK);
  set_reallocate_redraws(false);
  set_redraw_on_allocate(false);

  get_selection()->signal_changed().connect(sigc::mem_fun(*this, &TreeView::selection_on_changed));
}
TreeView::~TreeView() {
  if (tooltip_) delete tooltip_;
  if (cache_renderer_) g_hash_table_destroy(cache_renderer_);
  conn_hadj_value_changed_.disconnect();
  conn_vadj_value_changed_.disconnect();
}

//========================
// WidgetInputBase
bool WidgetInputBase::on_popup(guint /*button*/, guint32 /*activate_time*/) {
  return false;
}
bool WidgetInputBase::on_button_press_event(GdkEventButton *event) {
  if (input_->mouse()) {
    switch(event->button) {
    case 1:
      if (event->type == GDK_2BUTTON_PRESS) {
        if (run(input_->mouse()->double_click())) return true;
      }
      break;
    case 2:
      if (run(input_->mouse()->button2())) return true;
      break;
    case 3:
      if (on_popup(event->button, event->time)) return true;
      break;
    default:
      {
        ConfMain::RefCommand comm = input_->mouse()[event->button];
        if (comm && run(comm)) return true;
      }
      break;
    }
  }
  return false;
}
bool WidgetInputBase::on_key_press_event(GdkEventKey *event) {
  if (input_->key()) {
    if (event->type == GDK_KEY_PRESS) {
      if (input_->key()->run(app_, event->keyval,
                             static_cast<Gdk::ModifierType>(event->state),
                             sigc::mem_fun(*this, &WidgetInputBase::prepare_commandopts))) {
        return true;
      }
    }
  }
  return false;
}

bool WidgetInputBase::run(ConfMain::RefCommand comm) {
  const Glib::ustring &command = *comm->command();
  if (!command.empty()) {
    CommandOpts opts;
    prepare_commandopts(opts);
    app_.commandrunner().run(command, &opts);
    return true;
  }
  return false;
}

WidgetInputBase::WidgetInputBase(Application &app, RefInputEntry input) :
  app_  (app),
  input_(input)
{
}
WidgetInputBase::~WidgetInputBase() {
}

//========================
// SearchBox::TypeButton
void SearchBox::TypeButton::on_realize() {
  Gtk::MenuToolButton::on_realize();
  search_type_on_changed();
}
void SearchBox::TypeButton::on_clicked() {
  search_type_ = search_type_->next();
}

void SearchBox::TypeButton::enable_migemo_on_changed() {
  mi_migemo_.set_sensitive(Search::migemo_usable());
}

void SearchBox::TypeButton::search_type_on_changed() {
  switch(*search_type_) {
  case SEARCHTYPE_NORMAL:
    icon_.set_from_icon_name(IconManager::SEARCH_NORMAL,
                             Gtk::ICON_SIZE_SMALL_TOOLBAR);
    mi_normal_.set_active(true);
    break;
  case SEARCHTYPE_MIGEMO:
    icon_.set_from_icon_name(IconManager::SEARCH_MIGEMO,
                             Gtk::ICON_SIZE_SMALL_TOOLBAR);
    mi_migemo_.set_active(true);
    break;
  case SEARCHTYPE_REGEX:
    icon_.set_from_icon_name(IconManager::SEARCH_REGEX,
                             Gtk::ICON_SIZE_SMALL_TOOLBAR);
    mi_regex_.set_active(true);
    break;
  }
}
void SearchBox::TypeButton::mi_type_on_activate() {
  if (mi_normal_.get_active()) {
    search_type_ = SEARCHTYPE_NORMAL;
  } else if (mi_migemo_.get_active()) {
    search_type_ = SEARCHTYPE_MIGEMO;
  } else if (mi_regex_.get_active()) {
    search_type_ = SEARCHTYPE_REGEX;
  }
}

SearchBox::TypeButton::TypeButton(RefAttributeSearchType search_type) :
  search_type_(search_type),
  mi_normal_  (rbg_type_, _("_Normal"), true),
  mi_migemo_  (rbg_type_, _("_Migemo"), true),
  mi_regex_   (rbg_type_, _("_Regex"),  true)
{
  mi_normal_.signal_activate()
    .connect(sigc::mem_fun(*this, &SearchBox::TypeButton::mi_type_on_activate));
  mi_migemo_.signal_activate()
    .connect(sigc::mem_fun(*this, &SearchBox::TypeButton::mi_type_on_activate));
  mi_regex_.signal_activate()
    .connect(sigc::mem_fun(*this, &SearchBox::TypeButton::mi_type_on_activate));

  menu_.append(mi_normal_);
  menu_.append(mi_migemo_);
  menu_.append(mi_regex_);
  menu_.show_all();

  enable_migemo_on_changed();
  conn_enable_migemo_changed_ = Search::signal_enable_migemo_changed()
    .connect(sigc::mem_fun(*this, &TypeButton::enable_migemo_on_changed));

  search_type_on_changed();
  conn_search_type_changed_ = search_type_->signal_changed()
    .connect(sigc::mem_fun(*this, &TypeButton::search_type_on_changed));

  set_icon_widget(icon_);
  set_menu(menu_);
}
SearchBox::TypeButton::~TypeButton() {
  conn_enable_migemo_changed_.disconnect();
  conn_search_type_changed_.disconnect();
}

//========================
// SearchBox::Entry
void SearchBox::Entry::prepare_commandopts(CommandOpts &opts) {
  box_.prepare_commandopts(opts);
  opts.search = &box_;
}
void SearchBox::Entry::on_populate_popup(Gtk::Menu *menu) {
  WidgetInput<gpyp::Entry>::on_populate_popup(menu);
  if ((*box_.get_search()->type()) != SEARCHTYPE_REGEX) {
    Gtk::MenuItem *mi = new Gtk::MenuItem(_("Convert to _regex"), true);
    mi->signal_activate()
      .connect(sigc::mem_fun(box_, &SearchBox::convert_to_regex));
    mi->show();

    GtkWidget *sep = gtk_separator_menu_item_new();
    gtk_widget_show(sep);
    gtk_menu_append(menu->gobj(), sep);
    menu->append(*Gtk::manage(mi));
  }
}

SearchBox::Entry::Entry(Application             &app,
                        SearchBox               &box,
                        ConfInput::RefInputEntry input) :
  WidgetInput<gpyp::Entry>(app, input),
  box_                    (box)
{
}

//========================
// SearchBox
void SearchBox::prepare_commandopts(CommandOpts &/*opts*/) {
}

void SearchBox::on_show() {
  Gtk::HBox::on_show();
  search_->set(entry_.get_text());
  entry_.grab_focus();
}
void SearchBox::on_hide() {
  Gtk::HBox::on_hide();
  search_->set(Glib::ustring());
}

void SearchBox::entry_on_changed() {
  search_->set(entry_.get_text());
}

void SearchBox::convert_to_regex() {
  search_->convert_to_regex();
  conn_entry_changed_.block();
  entry_.set_text(search_->get_source());
  conn_entry_changed_.unblock();
}

SearchBox::SearchBox(Application             &app,
                     RefSearch                search,
                     ConfInput::RefInputEntry input) :
  app_       (app),
  search_    (search),
  typebutton_(search_->type()),
  entry_     (app, *this, input)
{
  conn_entry_changed_ = entry_.signal_changed()
    .connect(sigc::mem_fun(*this, &SearchBox::entry_on_changed));

  pack_start(typebutton_, false, true);
  pack_start(entry_);
  typebutton_.show();
  entry_.show();
}

//========================
// TextEditor
void TextEditor::prepare_commandopts(CommandOpts &opts) {
  opts.search      = &search_box_;
  opts.text_editor = this;
}

void TextEditor::search_on_changed() {
  editor_.search(search_->get());
}
void TextEditor::search_box_on_hide() {
  editor_.grab_focus();
}

TextEditor::TextEditor(Application &app) :
  WidgetInput<Gtk::VBox>(app, app.conf().input()->text_editor()),
  editor_               (app),
  search_               (app.conf().text_editor()->search()),
  search_box_           (app, search_, app.conf().input()->search())
{
  editor_.set_wrap_mode(Gtk::WRAP_WORD_CHAR);

  sw_editor_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw_editor_.add(editor_);
  sw_editor_.show();

  search_->signal_changed()
    .connect(sigc::mem_fun(*this, &TextEditor::search_on_changed));

  search_box_.signal_hide()
    .connect(sigc::mem_fun(*this, &TextEditor::search_box_on_hide));
  search_box_.set_no_show_all(true);

  pack_start(sw_editor_,  true,  true);
  pack_start(search_box_, false, true);
}

//========================
// TextEditorDialog
TextEditorDialog::TextEditorDialog(Application &app, Gtk::Window &parent) :
  Gtk::Dialog(_("Text editor"), parent),
  editor_    (app)
{
  Gtk::VBox *vbox = get_vbox();
  vbox->pack_start(editor_, true, true);
  vbox->show_all();

  add_button(Gtk::Stock::OK,     Gtk::RESPONSE_OK);
  add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
  resize(480, 320);
}

//========================
// MessageDialog
MessageDialog::MessageDialog(Gtk::MessageType     type,
                             const Glib::ustring &message1,
                             const Glib::ustring &message2) :
  Gtk::MessageDialog(message1, false, type)
{
  set(message2);
}
MessageDialog::MessageDialog(Gtk::Window         &parent,
                             Gtk::MessageType     type,
                             const Glib::ustring &message1,
                             const Glib::ustring &message2) :
  Gtk::MessageDialog(parent, message1, false, type)
{
  set(message2);
}

//========================
// WarningDialog
WarningDialog::WarningDialog(const Glib::ustring &message) :
  MessageDialog(Gtk::MESSAGE_WARNING, _("Warning"), message)
{
}
WarningDialog::WarningDialog(Gtk::Window         &parent,
                             const Glib::ustring &message) :
  MessageDialog(parent, Gtk::MESSAGE_WARNING, _("Warning"), message)
{
}

//========================
// ErrorDialog
ErrorDialog::ErrorDialog(const Glib::ustring &message) :
  MessageDialog(Gtk::MESSAGE_ERROR, _("Error"), message)
{
}
ErrorDialog::ErrorDialog(Gtk::Window         &parent,
                         const Glib::ustring &message) :
  MessageDialog(parent, Gtk::MESSAGE_ERROR, _("Error"), message)
{
}

}/*gpyp*/
